from tree import *
from event import *
from profiles import *


def readData(filename):
    file_ = open(filename)
    t = file_.readlines()
    m = readText(t)
    file_.close()
    return m

def readText(txt):
    m= []
    for line in txt:
        line = line.strip()
        k = []
        if line[0] == "T":
            id_, space_sep, mention = line.split('\t')
            k.append(id_)
            k.extend(space_sep.split())
            k.append(mention)
            k[2] = int(k[2])
            k[3] = int(k[3])
        elif line[0] in "EM":
            id_, space_sep = line.split('\t')
            k.append(id_)
            k.extend(space_sep.split())
        if k: m.append(k)
    return m


def abstrac2forest(pmid, prot_ignore_pmid=False, trig_ignore_pmid=False, ignore_prot=None, ignore_trig=None): # Arguments added by Jonathan.
    f = open(pmid + '.txt')
    text_ = f.read()
    f.close()
    
# ==================================================================
# Updated by Jonathan from here...

    #proteins = readData(pmid + '.a1')
    proteins_ = readData(pmid + '.a1')

    if ignore_prot:
        proteins = proteins_[:ignore_prot[0]]
        for i in range(len(ignore_prot)-1):
            if ignore_prot[i]+1 < ignore_prot[i+1]:
                proteins += proteins_[ignore_prot[i]+1:ignore_prot[i+1]]
        proteins += proteins_[ignore_prot[-1]+1:]
    else:
        proteins = proteins_

    gold = readData(pmid + '.a2')
    triggers_ = [i for i in gold if i[0].startswith('T')]
    events = [i for i in gold if i[0].startswith('E')]
    if events == []:
        return (0, 0, 0, 0, 0)
    context = [i for i in gold if i[0].startswith('M')]

    if ignore_trig:
        triggers = triggers_[:ignore_trig[0]]
        for i in range(len(ignore_trig)-1):
            if ignore_trig[i]+1 < ignore_trig[i+1]:
                triggers += triggers_[ignore_trig[i]+1:ignore_trig[i+1]]
        triggers += triggers_[ignore_trig[-1]+1:]
    else:
        triggers = triggers_
#                               ...to here.
# ==================================================================

    f = open(pmid + '.pstree')
    parse = f.readlines()
    f.close()
    text = text_
    position = 0 
    prot_pos = 0    # how far we've gone into the proteins list
    trig_pos = 0      # how far we've gone into the triggers list
    # I'll tag lines that handle proteins that are longer than one token with LONGP.
    
    forest = []

    for sent_ in parse:
        sent = sent_.strip()
        tree = make_tree(sent)
        ERROR = False
        #if tree:
        #    for i in tree.items():
        #        tagtypes.add(i.get_tag())
        if tree:
            for w in tree.leaves():
                work_on_current_word = True
                #print "word is " + w.data['text']
                while (work_on_current_word):
                    work_on_current_word = False
                    is_prot = is_trig = False
                    word_length = len(w.data['text'])
                    #print "looking for " + w.data['text'] + " in the loop"
                    #print text[position:position + 15]
                    position += text[position:].index(w.data['text'])
                    #print position
                    "marking proteins"
                    if prot_pos < len(proteins):                  # if there's still any proteins to match
                        prot_start = proteins[prot_pos][2]
                        prot_end = proteins[prot_pos][3]
                        #print "***protein is " + proteins[prot_pos][4]
                        #print "prot_start & _end are %d, %d" %(prot_start, prot_end)
                        # print w.data
                        # print "position is %d. Word length is %d" %(position, word_length)
                        # print "position >= prot_start? %d" %(position >= prot_start
                        # print "position + word_length <= prot_end? %d" %(position + word_length <= prot_end)
                        if (position >= prot_start and position + word_length <= prot_end) or \
                            (prot_start >= position and prot_end <= position + word_length): # to handle sub-token proteins!
                            #print "IS PROT!"
                            is_prot = True
                            prot_name = proteins[prot_pos][0]
                            #print prot_name,
                            #print position,
                            #print word_length,
                            #print prot_end
                            if position + word_length >= prot_end:  # end of this prot, including LONGP
                                #print "*"
                                prot_pos += 1                       # go to the next protein or the end of list
                                #print "prot_pos is now " + str(prot_pos)
                                work_on_current_word = True         # to handle >1 protein in the same token e.g. "p45/p60"
                        else:
                            is_prot = False
                            #print "isn't prot :("
                    # print "now looking for %d - %d" %(prot_start, prot_end)
                    
                    "marking triggers, just the same"
                    if trig_pos < len(triggers):                    # if there's still any triggers to match
                        trig_start = triggers[trig_pos][2]
                        trig_end = triggers[trig_pos][3]
                        #print "***trigger is " + triggers[trig_pos][4]
                        #print "trig_start & _end are %d, %d" %(trig_start, trig_end)
                        if (position >= trig_start and position + word_length <= trig_end) or \
                            (trig_start >= position and trig_end <= position + word_length): # to handle sub-token proteins!
                            is_trig = True
                            trig_name =  triggers[trig_pos][0]
                            if position + word_length >= trig_end:  # end of this prot, including LONGP
                                trig_pos += 1                       # go to the next trigger or the end of list
                                work_on_current_word = True
                                #print "**"
                        else:
                            is_trig = False
                    w.set_start(position)
                    #print position
                    w.set_end(position + word_length)
                    if not w.data['is_prot']:       # in case it goes through the word more than once, we don't want these fields to reset the second time
                        w.set_is_prot(is_prot)
                    if not w.data['is_trig']:
                        w.set_is_trig(is_trig)
                    if is_prot:
                        w.data['prot_names'] = w.data.setdefault('prot_names', '') + prot_name + ";" 
                    if is_trig:
                        w.data['trig_names'] = w.data.setdefault('trig_names', '') + trig_name + ";"
                    #print w.data 
                "end of work_on_current while loop."
                #if is_prot or is_trig:
            forest.append(tree)

            #print tree
        else:
            "it's not a tree!"
            ERROR = True
    if trig_pos + 1 < len(triggers):
        #print "error in triggers in " + pmid 
        ERROR = True
# ======================================================================================================
# Updated by Jonathan from here...
        if not ignore_trig:
            ignore_trig = []
        ignore_trig.append(trig_pos+len(ignore_trig))
#        print ignore_trig, triggers[trig_pos]
        #print "*** Warning: %g out of %g triggers were ignored. ***" %(len(ignore_trig), len(triggers_))
        if len(ignore_trig) < len(triggers_) and trig_ignore_pmid == False:
            forest = abstrac2forest(pmid, prot_ignore_pmid, trig_ignore_pmid, ignore_prot, ignore_trig)
        else:
            print "*** Warning: completely failed to parse %s. ***" %pmid
#                                 ...to here.
# ======================================================================================================

    elif prot_pos + 1 < len(proteins):
        #print "error in proteins in " + pmid 
        ERROR = True
# ======================================================================================================
# Updated by Jonathan from here...
        if not ignore_prot:
            ignore_prot = []
        ignore_prot.append(prot_pos+len(ignore_prot))
#        print ignore_prot, proteins[prot_pos]
        #print "*** Warning: %g out of %g proteins were ignored. ***" %(len(ignore_prot), len(proteins_))
        if len(ignore_prot) < len(proteins_) and prot_ignore_pmid == False:
            forest = abstrac2forest(pmid, prot_ignore_pmid, trig_ignore_pmid, ignore_prot, ignore_trig)
        else:
            print "*** Warning: completely failed to parse %s. ***" %pmid
#                                 ...to here.
# ======================================================================================================
    return forest

def build_events(events, context, forest):
    "The inputs are events, context, and forest."
    "events and contexts are directly read from a2 files"
    "and are written from tab-separated text into lists."
    "forest is a collection of parse trees for sentences."
    "The output is a list of every event in the sentence"
    "as Event object with proper theme & trigger links."
    molec_events = []
    for eve in events:
        e = Event(eve[0], eve[1].split(':')[0], eve[1].split(':')[1] )
        for theme in eve[2:]:
            e.add_partic_name(tuple(theme.split(':')))

        "now all the event names are extracted."
        "we set the negations"
        for con in context:
            if con[1] == "Negation" and con[2] == e.name:
                e.set_negated(True)

        "we go through the sentence trees in the abstract forest tree"
        "and look for triggers and participants to link the events to."
        for tree in forest:
            for node in tree.items():
                if 'prot_names' in node.data.keys():    #not needed
                    for i in e.get_partic_names():
                        if i[0].startswith('Theme') and i[1] in node.data['prot_names']:
                            e.participants.append(node)
                            # FIXME: this adds everything with the same name as a new participant. buggy until grouping is implemented
                        elif i[0].startswith('Cause') and i[1] in node.data['prot_names']:
                            e.causes.append(node)
                if 'trig_names' in node.data.keys():    #not needed
                    if e.get_trig_name() in node.data['trig_names']:
                        e.set_trigger(node)


        molec_events.append(e)

    # now adding nested events
    for eve1 in molec_events:
        parts = eve1.get_partic_names()
        
        for p in parts:
            for eve2 in molec_events:
                if eve2.get_name() == p[1] and p[0].startswith('Theme'):
                    eve1.participants.append(eve2)
                elif eve2.get_name() == p[1] and p[0].startswith('Cause'):
                    eve1.causes.append(eve2)

                    
    return molec_events

def check_command(molec_events, X = "S"):
    "check if command holds, and for what."
    "calculate statistics for the command relation"
    "This is the rule-based method. No ML"
    total = has_cue = com_tr = com_pr = com_both = 0
    eve_classes= {"I": 0, "II": 0, "III": 0}
    
    #print len(molec_events)
    for e in molec_events:
        '''
        "Counting events in each class"
        if not (e.get_negated() ^ ON_NEGATIONS):
            for c in CLASSES.keys():
                if e.get_type() in CLASSES[c]:
                    eve_classes[c] += 1
                    break
        '''
        if not (e.get_negated() ^ ON_NEGATIONS) and e.get_trigger():     #change ON_NEGATIONS in profile2 to do it on affirmative instances
            total += 1
            #print e
            #print e.get_trigger()
            anycue = any_com_tr = any_com_pr = False
            tree = e.get_trigger().get_root()
    
            for i in tree.items():
                if i.data['neg_cue']:
                    anycue = True
                    #print i.data['text']
                    if tree.commands(i, e.get_trigger(), X):
                        any_com_tr = True
                    for p in e.get_participants():
                        if type(p) == Node:
                            if tree.commands(i, p, X):
                                any_com_pr = True
                        elif type(p) == Event:
                            #print "*** " + e.get_type()
                            nested_trig = p.get_trigger()
                            if tree.commands(i, nested_trig, X):
                                any_com_pr = True

    
            if anycue:
                has_cue += 1
            if any_com_tr:
                com_tr += 1
            if any_com_pr:
                com_pr += 1
            if  any_com_tr and any_com_pr:
                com_both += 1

    return (total, has_cue, com_tr, com_pr, com_both, eve_classes["I"], eve_classes["II"], eve_classes["III"], )

def extract_features(molecular_events, classs = CLASS, X = ["S"]):
    "takes a list of molecular events,"
    "builds the feature vector for each"
    "and returns a list that contains features for every event"
    event_list_features = []
    for e in molecular_events:
        if e.get_type() in CLASSES[classs]:
            feature = []
            #initialisations
            cue_type = len(NEGATION_CUES)                   # largest possible number for no cue
            any_cue = False
            cue_tag = trig_tag = part_tag = ''
            common_parent_tag = ''
            cue_commands_p = cue_commands_t = [False] * len(X)
            dist_t_cue = dist_p_cue = surf_dist_t_cue = surf_dist_p_cue = MAX_DIST 
            cue = None

            if e.get_negated():
                feature.append(1)
            else:
                feature.append(0)
            feature.append(e.get_type())

            #anycue = any_com_tr = any_com_pr = False
            if e.get_trigger():
                tree = e.get_trigger().get_root()
                #same_VP_cue_tr = same_NP_cue_tr = False
                trig_tag = e.get_trigger().get_tag()
                #p_in_tree = True
                for i in tree.items():
                    if i.data['neg_cue']:
                        any_cue = True
                        cue = i
                        cue_tag = cue.get_tag()
                        cue_type = NEGATION_CUES.index(i.data['text'].lower())
                        '''
                        if tree.commands(i, e.get_trigger(), 'VP') and tree.commands(e.get_trigger(), i, 'VP'):
                            same_VP_cue_tr = True
                        if tree.commands(i, e.get_trigger(), 'NP') and tree.commands(e.get_trigger(), i,  'NP'):
                            same_NP_cue_tr = True
                        '''
                        common_parent_tag = tree.youngest_common_parent(e.get_trigger(), cue).get_tag()
                        for x in range(len(X)):
                            if tree.commands(e.get_trigger(), cue, X[x]):
                                cue_commands_t[x] = True
                        dist_t_cue = tree.distance(e.get_trigger(), cue)
                        surf_dist_t_cue = tree.surface_distance(e.get_trigger(), cue)

                        if len(e.get_participants()) > 0:
                            p = e.get_participants()[0]
                            for p in e.get_participants():
                                if type(p) == Node:
                                    part_tag = p.get_tag()
                                    surf_dist_p_cue = tree.surface_distance(p, cue)
                                    try:
                                        dist_p_cue = tree.distance(p, cue)
                                    except:
                                        1
                                    break
                                    for x in range(len(X)):
                                        if tree.commands(cue, p, X[x]):
                                            cue_commands_p[x] = True
                                    #if not p in tree.items():
                                        #p_in_tree = False
                                        #p_common_parent_tag = tree.youngest_common_parent(p, i).get_tag()
                                        #dist_p_cue = tree.distance(p, i)

                                elif type(p) == Event and p.get_trigger():
                                    part_tag = p.get_trigger().get_tag()
                                    nested_trig = p.get_trigger()
                                    surf_dist_p_cue = tree.surface_distance(nested_trig, cue)
                                    try:
                                        dist_p_cue = tree.distance(nested_trig, cue)
                                    except:
                                        1
                                    for x in range(len(X)):
                                        if tree.commands(cue, nested_trig, X[x]):
                                            cue_commands_p[x] = True

                                    #if not nested_trig in tree.items():
                                    #    p_in_tree = False
                                    #dist_p_cue = tree.distance(nested_trig, i)
                                    #p_common_parent_tag = tree.youngest_common_parent(nested_trig, i).get_tag()
                                break

            event_id = e.get_name()
            feature.extend([any_cue, cue_type, cue_tag, trig_tag, part_tag, common_parent_tag, cue_commands_p, cue_commands_t, dist_t_cue, surf_dist_t_cue, surf_dist_p_cue, dist_p_cue])   

            if classs == 3:
                cause_tag = ''
                theme_type = cause_type = 10         # default value. 0 - 9 are for protein and 9 event types
                dist_cue_c = surf_dist_c_cue = MAX_DIST 
                cue_commands_c = [False] * len(X)
                if len(e.get_participants()) > 0:
                    p = e.get_participants()[0]
                    if type(p) == Node:
                        theme_type = 9
                    elif type(p) == Event:
                        theme_type = etypes[p.get_type()]
                    else:
                        print type(p)
                        sys.exit(0)

               
                if len(e.get_causes()) > 0:
                    p = e.get_causes()[0]
                    if type(p) == Node:
                        cause_type = 9
                        cause_tag = p.get_tag()
                        if cue and e.get_trigger():
                            tree = e.get_trigger().get_root()
                            try:
                                dist_cue_c = tree.distance(cue, p)
                            except:
                                1
                            surf_dist_c_cue = tree.surface_distance(p, cue)
                            for x in range(len(X)):
                                if tree.commands(cue, p, X[x]):
                                    cue_commands_c[x] = True

                    elif type(p) == Event:
                        cause_type = etypes[p.get_type()]
                        if p.get_trigger():
                            cause_tag = p.get_trigger().get_tag()
                            if cue and e.get_trigger():
                                tree = e.get_trigger().get_root()
                                try:
                                    dist_cue_c = tree.distance(cue, p)
                                except:
                                    1
                                surf_dist_c_cue = tree.surface_distance(p, cue)

                                for x in range(len(X)):
                                    if tree.commands(cue, p.get_trigger(), X[x]):
                                        cue_commands_c[x] = True
                                
                    else:
                        print type(p)
                        sys.exit(0)
 
                feature.extend([theme_type, cause_type, cause_tag, cue_commands_c, surf_dist_c_cue, dist_cue_c,])
            
            feature.append(event_id)

            event_list_features.append(feature)
    return event_list_features

def extract_tags():
    tagtypes = set()
    '''
    if tree:
        for i in tree.items():
            tagtypes.add(i.get_tag())
    '''
    tags = open ('features/tagtypes.py', 'w')
    tags.write(repr(tagtypes))
    tags.close()

def feat2binary():
    if PARSER == "McClosky":
        tags = McClosky_tags
    elif PARSER == "Bikel":
        tags = "Bikel_tags"

    if TRAINING:
        f = open('features/training' + str(CLASS) + '.features')
    else:
        f = open('features/development' + str(CLASS) + '.features')
    input = f.readlines()
    f.close()
    feats = []

    for i in input:
        feats. append(eval(i.split(';')[0]))

    binfeats = [] 

    for efeats in feats:
        binf = []
        binf.append(efeats [0])                             # index 0 is the target: negation. add 1 if using milticlass because there is no class 0
        
        
        
        for i in CLASSES.keys():                            # event type
            for j in etypes.keys():
                if efeats[1] in CLASSES[i] and j in CLASSES[i]:
                    if efeats[1] == j:
                        binf.append(1)
                    else:
                        binf.append(0)
                        
        
        #for i in CLASSES.keys():                                # different classes - used in the single SVM - keep commented out
        #    if efeats[1] in CLASSES[i]:                      
        #        binf.append(1)                      
        #    else:
        #        binf.append(0)
        
        
        if efeats[2]:                                       # the existence of neg cue; feature 2
            binf.append(0)
        else:
            binf.append(1)
        
        for i in range(len(NEGATION_CUES)+ 1):              # the type of cue (32 cues and one for no cue), feature 3
            if efeats[3] == i:
                binf.append(1)
            else:
                binf.append(0)
        
        for i in tags:                                      # feature 4 (cue tag)
            if i == efeats[4]:
                binf.append(1)
            else:
                binf.append(0)

        for i in tags:                                      # feature 5 (trig tag)
            if i == efeats[5]:
                binf.append(1)
            else:
                binf.append(0)

        for i in tags:                                      # feature 6 (part tag)
            if i == efeats[6]:
                binf.append(1)
            else:
                binf.append(0)

        
        
        for i in tags:                                      # feature 7 (common parent tag)
            if i == efeats[7]:
                binf.append(1)
            else:
                binf.append(0)
        
        
        for x in efeats[8]:
            if x:
                binf.append(1)                            # feature 8; cue_commands_p for different X's
            else:
                binf.append(0)
        
        for x in efeats[9]:
            assert x in [True, False]
            if x:
                binf.append(1)                            # feature 9; cue_commands_t for different X's
            else:
                binf.append(0)

        
        binf.append(efeats[10])                         # tree dist_t_cue
        '''
        binf.append(efeats[11])                         # surf_dist_t_cue
        binf.append(efeats[12])                         # surf_dist_p_cue
        '''
        
        binf.append(efeats[13])                         # dist_p_cue
        
        
        if CLASS == 3:
            
            
            for i in range(10):                         # theme_type for class 3
                if i == efeats[14]:
                    binf.append(1)
                else:
                    binf.append(0)
            
            for i in range(10):                         # cause_type for class 3
                if i == efeats[15]:
                    binf.append(1)
                else:
                    binf.append(0)
               
            for i in tags:                                      # feature 14 (cause POS tag) for class 3
                if i == efeats[16]:
                    binf.append(1)
                else:
                    binf.append(0)
            
            
            for x in efeats[17]:
                assert x in [True, False]
                if x:
                    binf.append(1)                            # feature 14; cue_commands_c for different X's for class 3
                else:
                    binf.append(0)
            
            '''
            binf.append(efeats[18])                         # surf_dist_c_cue for cause
            '''
            binf.append(efeats[19])                         # dist_cue_c for cause
            
            
        # p_in_tree
        binfeats.append(binf)


    if FORMAT == 'CSV':
        if TRAINING:
            f = open('features/training' + str(CLASS) + '.csv', 'w')
        else:
            f = open('features/development' + str(CLASS) + '.csv', 'w')
    
        for i in range(1, len(binfeats[0])):
            f.write(str(i) + '\t')

        f.write('\n')    
    
        for i in binfeats:
            for j in range(1,len(i)):
                #f.write(str(j) + ':' + str(i[j]) + '\t')
                f.write(str(i[j]) + '\t')
            
            f.write(str(i[0]))
            f.write('\n')

    elif FORMAT == 'SVM light':
        if TRAINING:
           f = open('features/training' + str(CLASS) + '.dat', 'w')
        else:
            f = open('features/development' + str(CLASS) + '.dat', 'w')

        for i in binfeats:
            f.write(str(i[0]) + ' ')
            for j in range(1,len(i)):
                f.write(str(j) + ':' + str(i[j]) + ' ')
            f.write('\n')

        #f.write(str(i[0]) + '\t')
        '''
        #f.write('\n')
        if i[0] == 0:
            #f.write('yes type:')
            f.write('1 1:')
        else:
            #f.write('no type:')
            f.write('0 1:')
        #f.write(str(i[0]) + ' ')
        if i[1] == 0:
            #f.write('yes')
            f.write('1')
        else:    
            #f.write('no')
            f.write('0')
        '''

    f.close()   
